// Learn TypeScript:
//  - https://docs.cocos.com/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/manual/en/scripting/life-cycle-callbacks.html

import AstarCommand from "./AstarCommand";
import AstarNode from "./AstarNode";
import BHeapModel from "./BHeapModel";

const { ccclass, property } = cc._decorator;

@ccclass
export default class AStar {
    // private _endNode: AstarNode;                  //终点Node
    // private _startNode: AstarNode;                //起点Node
    // private _path: cc.Vec2[];               //保存路径
    private _straightCost: number = 1.0;     //上下左右走的代价
    private _diagCost: number = Math.SQRT2;  //斜着走的代价 
    private _nodes: AstarNode[][];  //Node数组
    private _mapCols: number;
    private _mapRows: number;
    private static _instance: AStar;
    private astarCommands: AstarCommand[] = [];
    private curAstarCommand: AstarCommand;
    private intervalId: any;
    private RamainForNum = 50;

    public static get instance() {
        if (!AStar._instance) {
            AStar._instance = new AStar();
        }
        return AStar._instance;
    }

    public init(mapRows: number, mapCols: number, leftBottomX: number, leftBottomY: number, spaceX: number, spaceY: number) {
        this._mapRows = mapRows;
        this._mapCols = mapCols;
        this._nodes = [];
        for (var i = 0; i < mapCols; i++) {
            this._nodes[i] = [];
            for (var j = 0; j < mapRows; j++) {
                let node = new AstarNode();
                node.x = i;
                node.y = j;
                node.mapX = leftBottomX + i * spaceX;
                node.mapY = leftBottomY + j * spaceY;
                this._nodes[i][j] = node;
            }
        }
    }

    public setWalkable(x: number, y: number, value: boolean) {
        this._nodes[x][y].walkable = value;
    }

    public pushFindPathCommand(startX: number, startY: number, endX: number, endY: number, ownerId, cb:Function, interruptPrev: boolean = true) {
        if (interruptPrev) {
            for (let i = 0; i < this.astarCommands.length; i++) {
                let command = this.astarCommands[i];
                if (command.ownerId == ownerId) {
                    this.stopCommand(command);
                    i--;
                }
            }
        }
        let astarCommand: AstarCommand = new AstarCommand();
        astarCommand.startX = startX;
        astarCommand.startY = startY;
        astarCommand.endX = endX;
        astarCommand.endY = endY;
        astarCommand.ownerId = ownerId;
        astarCommand.cb = cb;
        astarCommand.id = this.astarCommandId;
        this.astarCommands.push(astarCommand)
        if(this.intervalId == null){
            this.intervalId = setInterval(this.update.bind(this), 1/60);
        }
        return astarCommand.id;
    }

    private _astarCommandId: number = 0;
    private get astarCommandId() {
        return ++this._astarCommandId;
    }

    private startCommand(astarCommand: AstarCommand) {
        this.curAstarCommand = astarCommand;
        this.curAstarCommand.started = true;
        astarCommand.startNode = this._nodes[astarCommand.startX][astarCommand.startY];
        astarCommand.endNode = this._nodes[astarCommand.endX][astarCommand.endY];
        BHeapModel.instance.reset();
        for (var i = 0; i < this._nodes.length; i++) {
            for (var j = 0; j < this._nodes[i].length; j++) {
                this._nodes[i][j].isInCloseList = false;
                this._nodes[i][j].isInOpenList = false;
            }
        }

        if (!astarCommand.startNode || !astarCommand.endNode) return null;
        if (!astarCommand.endNode.walkable) return null;
        if (!astarCommand.startNode.walkable) return null;

        astarCommand.startNode.g = 0;
        astarCommand.startNode.h = this.heuristic(astarCommand.startNode, astarCommand);
        astarCommand.startNode.f = astarCommand.startNode.g + astarCommand.startNode.h;
        astarCommand.remainForNum = this.RamainForNum;

        this.curAstarCommand.prevAstarNode = this.curAstarCommand.startNode;
        
        this.search1();
    }

    private stopCommand(astarCommand: AstarCommand) {
        let index = this.astarCommands.indexOf(astarCommand);
        if (index >= 0) {
            this.astarCommands.splice(index, 1);
        }
        if(astarCommand == this.curAstarCommand){
            this.curAstarCommand = null;
        }
    }

    private search1() {
        while (this.curAstarCommand.prevAstarNode != this.curAstarCommand.endNode) {
            var node = this.curAstarCommand.prevAstarNode;
            var startX = Math.max(0, node.x - 1);
            var endX = Math.min(this._mapCols - 1, node.x + 1);
            var startY = Math.max(0, node.y - 1);
            var endY = Math.min(this._mapRows - 1, node.y + 1);
            for (var i = startX; i <= endX; i++) {
                for (var j = startY; j <= endY; j++) {
                    // if (i != node.x && j != node.y) {
                    // 	continue;
                    // }
                    var testNode = this._nodes[i][j];
                    if (testNode == node
                        || !testNode.walkable
                        || !this._nodes[node.x][testNode.y].walkable
                        || !this._nodes[testNode.x][node.y].walkable) {
                        continue;
                    }

                    var cost: number = this._straightCost;
                    if (!((node.x == testNode.x) || (node.y == testNode.y))) {
                        cost = this._diagCost;
                    }
                    var g = node.g + cost * testNode.costMultiplier;
                    var h = this.heuristic(testNode, this.curAstarCommand);
                    var f = g + h;
                    if (testNode.isInOpenList || testNode.isInCloseList) {
                        if (testNode.f > f) {
                            testNode.g = g;
                            testNode.h = h;
                            testNode.f = f;
                            testNode.parentNode = node;
                        }
                    } else {
                        testNode.g = g;
                        testNode.h = h;
                        testNode.f = f;
                        testNode.parentNode = node;
                        // this._open.push(testNode);
                        testNode.isInOpenList = true;
                        BHeapModel.instance.push(testNode);
                    }
                }
            }
            node.isInCloseList = true;
            // if (BHeapModel.instance.len <= 1) {
            // 	return false;
            // }

            if (BHeapModel.instance.head) {
                this.curAstarCommand.prevAstarNode = BHeapModel.instance.shift();
            } else {
               break;
            }
            if (this.curAstarCommand.remainForNum > 0) {
                this.curAstarCommand.remainForNum -= 1;
                if (this.curAstarCommand.remainForNum <= 0) {
                    return;
                }
            }
            // console.log("f值最小的点", node.x, node.y)
        }
        this.buildPath();
    }

    //获取路径
    private buildPath() {
        var path = new Array();
        var node: AstarNode = this.curAstarCommand.endNode
        if(node.parentNode){
            path.push(node.mapPos);
            while (node != this.curAstarCommand.startNode) {
                node = node.parentNode;
                path.unshift(node.mapPos);
            }
        }
        this.curAstarCommand.path = path;
        this.curAstarCommand.cb(path);
        this.stopCommand(this.curAstarCommand);
        // console.log("buildPath", this._path)
    }

    // //是否待检查
    // private isOpen(node: AstarNode): boolean {
    //     for (var i = 1; i < BHeapModel.instance.len; i++) {
    //         if (BHeapModel.instance.bHeapList[i].astarNode == node) {
    //             return true;
    //         }
    //     }
    //     return false;
    // }

    // //是否已检查
    // private isClosed(node: AstarNode): boolean {
    //     for (var i = 0; i < this._closed.length; i++) {
    //         if (this._closed[i] == node) {
    //             return true;
    //         }
    //     }
    //     return false;
    // }

    private heuristic(node: AstarNode, astarCommand: AstarCommand) {
        var dx = Math.abs(node.x - astarCommand.endNode.x);
        var dy = Math.abs(node.y - astarCommand.endNode.y);
        return this._diagCost * Math.min(dx, dy) + this._straightCost * Math.abs(dx - dy);
    }

    update(dt) {
        if(this.curAstarCommand && this.curAstarCommand.started && this.curAstarCommand.prevAstarNode != this.curAstarCommand.endNode){
            this.curAstarCommand.remainForNum = this.RamainForNum;
            this.search1();
            return;
        }
        if (this.astarCommands.length > 0 && !this.curAstarCommand) {
            this.startCommand(this.astarCommands.shift())
        }
    }


}
